package se.emilsjolander.library;

import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.List;

import static ohos.agp.animation.Animator.CurveType.CYCLE;
import static ohos.agp.animation.Animator.CurveType.LINEAR;
import static ohos.agp.animation.Animator.INFINITE;

public class FlipView extends Component implements Component.DrawTask,
    Component.TouchEventListener {
  private static final long ANIMATION_DURATION_PER_ITEM = 600L;
  private FoldShading foldShading;
  private FoldableAdapter adapter;
  private boolean isGesturesEnabled = true;
  private OnFoldRotationListener foldRotationListener;
  private AnimatorValue animator;
  private Paint paint;

  private float foldRotation;
  private float minRotation;
  private float maxRotation;
  private float touchX;
  private float touchY;

  private long lastTime;
  private int speed;
  private String clickName = "";
  private boolean firstIn = true;


  public FlipView(Context context) {
    this(context, null);
  }

  public FlipView(Context context, AttrSet attrSet) {
    this(context, attrSet, null);
  }

  public FlipView(Context context, AttrSet attrSet, String styleName) {
    super(context, attrSet, styleName);
    init();
  }

  private void init() {
    animator = new AnimatorValue();
    addDrawTask(this);
    setTouchEventListener(this);
  }

  public void setOnFoldRotationListener(OnFoldRotationListener listener) {
    foldRotationListener = listener;
  }

  public void setFoldShading(FoldShading shading) {
    foldShading = shading;
    invalidate();
  }

  public void setGesturesEnabled(boolean isGesturesEnabled) {
    this.isGesturesEnabled = isGesturesEnabled;
  }

  public void setAdapter(FoldableAdapter adapter) {
    if (adapter != null && adapter.foldableItems != null) {
      this.adapter = adapter;
      minRotation = -45;
      maxRotation = 180 * (adapter.foldableItems.size() - 1) + 45;
      invalidate();
    }
  }

  public FoldableAdapter getAdapter() {
    return adapter;
  }

  public int getCount() {
    return adapter == null ? 0 : adapter.getCount();
  }

  public float getFoldRotation() {
    return foldRotation;
  }

  public final void setFoldRotation(float rotation) {
    setFoldRotation(rotation, false);
  }

  protected void setFoldRotation(float rotation, boolean isFromUser) {
    if (isFromUser) {
      animator.cancel();
    }
    rotation = Math.min(Math.max(minRotation, rotation), maxRotation);
    foldRotation = rotation;
    if (foldRotationListener != null) {
      foldRotationListener.onFoldRotation(rotation, isFromUser);
    }
    invalidate();
  }

  public int getPosition() {
    return Math.round(foldRotation / 180f);
  }

  public void scrollToPosition(int index) {
    index = Math.max(0, Math.min(index, getCount() - 1));
    animateFold(index * 180f);
  }

  protected void scrollToNearestPosition() {
    scrollToPosition((int) ((getFoldRotation() + 90f) / 180f));
  }

  protected void animateFold(float to) {
    animateFold(to, 180);
  }

  public void animateFold(float to, int speed) {
    final float from = getFoldRotation();
    final long duration = (long) Math.abs(ANIMATION_DURATION_PER_ITEM * (to - from) / speed);
    animator.stop();

    animator.cancel();
    animator.setValueUpdateListener((animatorValue, v) -> {
      foldRotation = from + (to - from) * Math.abs(v);
      invalidate();
    });
    animator.setCurveType(LINEAR);
    animator.setDuration(duration);
    animator.setLoopedCount(0);
    animator.start();
  }

  private void initAnimateFold() {
    int to = 45;
    int from = 0;
    int speed = 10;
    final long duration = (long) Math.abs(ANIMATION_DURATION_PER_ITEM * (to - from) / speed);
    animator.stop();

    animator.cancel();
    animator.setValueUpdateListener((animatorValue, v) -> {
      foldRotation = from + (to - from) * Math.abs(v);
      invalidate();
    });
    animator.setCurveType(CYCLE);
    animator.setDuration(duration);
    animator.setLoopedCount(INFINITE);
    animator.start();
  }

  @Override
  public void onDraw(Component component, Canvas canvas) {
    int width = getWidth() - getPaddingLeft() - getPaddingRight();
    int height = getHeight() - getPaddingTop() - getPaddingBottom();
    int backgroundIndex = -1;
    int backgroundLayoutAlignment = -1;
    int threeDimIndex = -1;
    int threeDimLayoutAlignment = -1;
    int foregroundIndex = -1;
    int foregroundLayoutAlignment = -1;
    if (adapter != null && adapter.foldableItems != null && adapter.foldableItems.size() > 0) {
      for (int i = 0; i < adapter.foldableItems.size(); i++) {
        if (foldRotation > i * 180 - 180 && foldRotation < i * 180 + 180) {
          if (foldRotation == i * 180) {
            clickName = adapter.foldableItems.get(i).onDraw(canvas, width, height, touchX, touchY);
          } else if (foldRotation < i * 180) {
            if (about(foldRotation, i * 180 - 90)) {
              clickName = adapter.foldableItems.get(i).draw(canvas, width, height, 0,
                  LayoutAlignment.BOTTOM, foldShading, touchX, touchY);
              canvas.drawLine(0, height / 2, width, height / 2, new Paint(), Color.BLACK);
            } else if (foldRotation > i * 180 - 90) {
              threeDimIndex = i;
              threeDimLayoutAlignment = LayoutAlignment.TOP;
              foregroundIndex = i;
              foregroundLayoutAlignment = LayoutAlignment.BOTTOM;
            } else {
              backgroundIndex = i;
              backgroundLayoutAlignment = LayoutAlignment.BOTTOM;
            }
          } else if (foldRotation > i * 180) {
            if (about(foldRotation, i * 180 + 90)) {
              clickName = adapter.foldableItems.get(i).draw(canvas, width, height, 0,
                  LayoutAlignment.TOP, foldShading, touchX, touchY);
              canvas.drawLine(0, height / 2, width, height / 2, new Paint(), Color.BLACK);
            } else if (foldRotation < i * 180 + 90) {
              threeDimIndex = i;
              threeDimLayoutAlignment = LayoutAlignment.BOTTOM;
              foregroundIndex = i;
              foregroundLayoutAlignment = LayoutAlignment.TOP;
            } else {
              backgroundIndex = i;
              backgroundLayoutAlignment = LayoutAlignment.TOP;
            }
          }
        }
      }
    }
    if (backgroundIndex != -1 && backgroundLayoutAlignment != -1 && backgroundIndex < adapter.foldableItems.size()) {
      clickName = adapter.foldableItems.get(backgroundIndex).draw(canvas, width, height, 0,
          backgroundLayoutAlignment, foldShading, touchX, touchY);
    }
    if (foregroundIndex != -1 && threeDimLayoutAlignment != -1 && threeDimIndex < adapter.foldableItems.size()) {
      clickName = adapter.foldableItems.get(threeDimIndex).draw(canvas, width, height,
          foldRotation - threeDimIndex * 180, threeDimLayoutAlignment, foldShading, touchX, touchY);
    }
    if (foregroundIndex != -1 && foregroundLayoutAlignment != -1 && foregroundIndex < adapter.foldableItems.size()) {
      clickName = adapter.foldableItems.get(foregroundIndex).draw(canvas, width, height, 0,
          foregroundLayoutAlignment, foldShading, touchX, touchY);
    }
    if (firstIn) {
      initAnimateFold();
      firstIn = false;
    }
  }

  private boolean about(float a, float b) {
    if (Math.abs(a - b) < 0.1f) {
      return true;
    }
    return false;
  }

  @Override
  public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
    if (!isGesturesEnabled) {
      return false;
    }
    if (animator != null) {
      animator.stop();
    }
    if (touchEvent.getPointerCount() == 1) {
      if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
        speed = 0;
        lastTime = System.currentTimeMillis();
      } else if (touchEvent.getAction() == TouchEvent.POINT_MOVE) {
        float offset = getTouchY(touchEvent, 0) - touchY;
        float rotation = -180f * offset * 2 / getHeight();
        setFoldRotation(foldRotation + rotation, true);
        if (lastTime != 0) {
          speed = (int) (rotation * 1000 / (System.currentTimeMillis() - lastTime));
        }
        lastTime = System.currentTimeMillis();
        touchX = getTouchX(touchEvent, 0);
        touchY = getTouchY(touchEvent, 0);
      } else if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
        int from = (int) foldRotation;
        if (speed == 0 && "first".equals(clickName)) {
          firstClick();
        } else if (speed == 0 && "last".equals(clickName)) {
          lastClick();
        }
        if (speed > 180 || speed < -180) {
          int to;
          if (speed > 0) {
            to = getNextRotation(foldRotation);
          } else {
            to = getLastRotation(foldRotation);
          }
          animateFold(to, speed);
        } else {
          int to;
          if ("".equals(clickName)) {
            if (Math.abs(getNextRotation(foldRotation) - from) < Math.abs(getLastRotation(foldRotation) - from)) {
              to = getNextRotation(foldRotation);
            } else {
              to = getLastRotation(foldRotation);
            }
            animateFold(to);
          }
        }
      }
    }
    return true;
  }

  private void firstClick() {
    animateFold(0, 1000);
  }

  private void lastClick() {
    animateFold((getCount() - 1) * 180, 1000);
  }

  public static class FoldableAdapter {
    private List<FoldableItem> foldableItems;

    public FoldableAdapter(List<FoldableItem> foldableItems) {
      this.foldableItems = foldableItems;
    }

    public int getCount() {
      return foldableItems == null ? 0 : foldableItems.size();
    }
  }

  private int getLastRotation(float rotation) {
    return (int) rotation / 180 * 180;
  }

  private int getNextRotation(float rotation) {
    if (getLastRotation(rotation) == (int) rotation) {
      return getLastRotation(rotation);
    }
    if (rotation >= (getCount() - 1) * 180) {
      return (getCount() - 1) * 180;
    }
    return (int) rotation / 180 * 180 + 180;
  }

  private float getTouchX(TouchEvent touchEvent, int index) {
    float touchX = 0;
    if (touchEvent.getPointerCount() > index) {
      int[] xy = getLocationOnScreen();
      if (xy != null && xy.length == 2) {
        touchX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
      } else {
        touchX = touchEvent.getPointerPosition(index).getX();
      }
    }
    return touchX;
  }

  private float getTouchY(TouchEvent touchEvent, int index) {
    float touchY = 0;
    if (touchEvent.getPointerCount() > index) {
      int[] xy = getLocationOnScreen();
      if (xy != null && xy.length == 2) {
        touchY = touchEvent.getPointerScreenPosition(index).getY() - xy[1];
      } else {
        touchY = touchEvent.getPointerPosition(index).getY();
      }
    }
    return touchY;
  }

  public interface OnFoldRotationListener {
    void onFoldRotation(float rotation, boolean isFromUser);
  }
}
